package com.tanjie.blackbean.web.log;

import cn.hutool.json.JSONUtil;
import com.tanjie.blackbean.web.support.AnnotationExtractor;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.http.HttpHeaders;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import static com.tanjie.blackbean.web.log.LogPrinter.LOG_MDC_KEY;

/**
 * @author: tanjie
 * @date: 2022-04-07
 **/
@Slf4j
@Aspect
public class PrintLogAspect {

    @Resource
    HttpServletRequest request;

    @Value("${spring.application.name}")
    String appName;

    @Pointcut("@annotation(com.tanjie.blackbean.web.annotation.PrintLog)")
    private void annotationPointcut() {
    }

    @Pointcut("@within(com.tanjie.blackbean.web.annotation.PrintLog)")
    private void withinPointcut() {
    }

    @Pointcut("@within(com.tanjie.blackbean.web.annotation.BlackBeanController)")
    private void controllerPointcut() {
    }

    @Pointcut("annotationPointcut()||withinPointcut()||controllerPointcut()")
    private void pointcut() {

    }

    public static final String REQUEST_START_TIME = "requestStartTime";

    @AfterThrowing("pointcut()")
    public void afterThrowing(JoinPoint joinPoint) {
        long requestTime = (long) request.getAttribute(REQUEST_START_TIME);
        long timeConsuming = System.currentTimeMillis() - requestTime;
        ApiLogInfo logInfo = getApiLogInfo(joinPoint);
        logInfo.setTimeConsuming(timeConsuming);
        LogPrinter.print(logInfo);
        MDC.remove(LOG_MDC_KEY);
    }

    @Around("pointcut())")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //
        long startTime = System.currentTimeMillis();
        request.setAttribute(REQUEST_START_TIME, startTime);
        Object responseResultObject = joinPoint.proceed();
        long timeConsuming = System.currentTimeMillis() - startTime;
        ApiLogInfo logInfo = getApiLogInfo(joinPoint);
        logInfo.setResponseString(JSONUtil.toJsonStr(responseResultObject));
        logInfo.setTimeConsuming(timeConsuming);
        LogPrinter.print(logInfo);
        MDC.remove(LOG_MDC_KEY);
        return responseResultObject;
    }

    private ApiLogInfo getApiLogInfo(JoinPoint joinPoint) {
        //
        Signature signature = joinPoint.getSignature();
        String logTitle = getLogTitle(joinPoint);
        String requestArgs = JSONUtil.toJsonStr(getFieldsName(joinPoint));
        String handler = signature.getDeclaringTypeName() + signature.getName();
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String remoteAddr = request.getRemoteAddr();
        String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
        //
        ApiLogInfo logInfo = new ApiLogInfo();
        logInfo.setLogThreadId(MDC.get(LOG_MDC_KEY));
        logInfo.setAppName(appName);
        logInfo.setTitle(logTitle);
        logInfo.setUri(uri);
        logInfo.setMethod(method);
        logInfo.setTimestamp(System.currentTimeMillis());
        logInfo.setRemoteAddress(remoteAddr);
        logInfo.setUserAgent(userAgent);
        logInfo.setHandler(handler);
        logInfo.setRequestArgs(requestArgs);
        return logInfo;
    }

    private String getLogTitle(JoinPoint joinPoint) {
        StringBuilder sb = new StringBuilder();
        Tag tag = AnnotationExtractor.get(joinPoint, Tag.class);
        if (tag != null) {
            String name = tag.name();
            sb.append(name);
        }
        Operation operation = AnnotationExtractor.get(joinPoint, Operation.class);
        if (operation != null) {
            String summary = operation.summary();
            sb.append(" ").append(summary);
        }
        return sb.toString();
    }

    /**
     * 获取参数列表
     */
    private static Map<String, Object> getFieldsName(JoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(16);
        if (parameterNames == null) {
            return paramMap;
        }
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        return paramMap;
    }

}
